Skip to content

feat: force useId #741

Closed
yoyo837 wants to merge 5 commits intomasterfrom
forceReactUseIdIfExists
Closed

feat: force useId #741
yoyo837 wants to merge 5 commits intomasterfrom
forceReactUseIdIfExists

Conversation

@yoyo837
Copy link
Member

@yoyo837 yoyo837 commented Mar 18, 2026

Summary

  • Add forceUseId capability to useId so callers can opt out of the fixed 'test-id' behavior in test env.
  • Keep existing default behavior unchanged (useId() in test env still returns 'test-id') to avoid breaking current snapshots.

Motivation

Recent upstream changes in Portal (global keydown listener + stack dedupe by id) make some nested-portal tests sensitive to id collisions.

When every hook call returns 'test-id' in test env, multiple portals can share the same id in test runtime, which may hide or distort real behavior in stack-based logic.

forceUseId is introduced as an opt-in escape hatch for these specific scenarios:

  • preserve stable snapshots by default
  • allow targeted tests/components to use real React/compat ids when uniqueness matters

Backward Compatibility

  • No behavior change for existing callers.
  • New behavior only applies when forceUseId === true.

Tests

Added/updated tests to verify:

  1. React useId path: in test env with forceUseId=true, id is not 'test-id'.
  2. React 17 compat path (no native useId): in test env with forceUseId=true, id is not 'test-id'.

Summary by CodeRabbit

发布说明

  • 新特性

    • 在 useId 钩子中添加了可选参数,允许在测试环境中禁用模拟ID行为,提供了对ID生成更精细的控制。
  • 测试

    • 添加了新的测试用例,验证在测试环境中ID生成的不同配置行为。

Copilot AI review requested due to automatic review settings March 18, 2026 01:42
@vercel
Copy link

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
util Ready Ready Preview, Comment Mar 18, 2026 1:48am

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the useId hook by providing more granular control over its behavior, particularly within test environments. It introduces a forceUseId parameter, allowing developers to explicitly request a real ID generation even when NODE_ENV is set to 'test', which previously would always return a mock ID. This change provides flexibility for scenarios where a unique, non-mocked ID is required during testing.

Highlights

  • Enhanced useId Hook: Introduced a new optional forceUseId parameter to both the useId and useCompatId functions.
  • Test Environment Control: Modified the logic within test environments to conditionally bypass the default 'test-id' return, allowing a real ID to be generated when forceUseId is set to true.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Walkthrough

src/hooks/useId.ts 中为 useIduseCompatId 添加了第二个可选参数 disableTestMock?: boolean,并将测试环境下返回 'test-id' 的行为改为受该参数控制;新增/更新了两个测试以覆盖该行为分支。

Changes

Cohort / File(s) Summary
Hook 实现
src/hooks/useId.ts
useId(id?: string)useCompatId(id?: string) 的签名更新为 ...(id?: string, disableTestMock?: boolean);在 NODE_ENV === 'test' 分支中,仅当 disableTestMock 不为真时返回 'test-id',否则返回实际生成的 React id/innerId。
测试用例
tests/hooks.test.tsx, tests/hooks-17.test.tsx
新增测试用例:在测试环境下传入第二个参数(true)以强制使用真实的 React id,断言生成的 id 存在且不是 'test-id'resetUuid() 在相关测试中被调用以重置 id 状态。

Estimated code review effort

🎯 2 (简单) | ⏱️ ~8 分钟

诗句

🐰 小兔说:新参数悄然来,
测试假 id 今可裁,
若愿真实便点真,
小小改动大安心,
咯咯跑跳庆开怀。 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive 标题「feat: force useId」不够具体,缺少对核心变更的描述。虽然提到了「force useId」,但未说明是添加可选参数、改变测试环境行为还是其他内容。 建议改进标题,例如「feat: add disableTestMock option to useId hooks」或「feat: allow opting out of test-id in test environment」,以更清晰地说明变更内容。
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch forceReactUseIdIfExists
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a forceUseId parameter to bypass mock ID generation in test environments, which is a useful addition. My review has identified two main areas for improvement. First, the new functionality lacks corresponding tests. It is important to add tests to cover both the modern useId and the legacy useCompatId paths to ensure correctness and prevent regressions. Second, the name forceUseId could be clearer; I've suggested alternatives to improve code readability and maintainability. Addressing these points will strengthen the quality of this contribution.

Comment on lines 52 to 54
if (!forceUseId && process.env.NODE_ENV === 'test') {
return 'test-id';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This new logic is not covered by tests. Please add tests to verify that forceUseId correctly bypasses the mock ID generation in a test environment. This is important to ensure the feature works as expected and to prevent future regressions. The tests should cover both the useId and useCompatId implementations.

export default useOriginId
? // Use React `useId`
function useId(id?: string) {
function useId(id?: string, forceUseId?: boolean) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parameter name forceUseId can be a bit confusing. Its purpose is to bypass the mock ID in test environments, but the name might suggest it's forcing the use of the useId hook itself. A more descriptive name like forceActualId or skipMock would improve clarity. This change should be applied consistently to both useId and useCompatId and their usages.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in mechanism to bypass the test-id shortcut in useId, allowing callers to get a “real” generated id even when NODE_ENV === 'test'.

Changes:

  • Extend useId / compat useId to accept an optional forceUseId flag.
  • Skip returning 'test-id' in test env when the flag is enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 52 to 53
if (!forceUseId && process.env.NODE_ENV === 'test') {
return 'test-id';
Comment on lines 59 to 60
function useCompatId(id?: string, forceUseId?: boolean) {
// Inner id for accessibility usage. Only work in client side
Comment on lines +75 to +76
// By default, test env always return mock id, but also allow force use id for test case which need to test id logic.
if (!forceUseId && process.env.NODE_ENV === 'test') {
export default useOriginId
? // Use React `useId`
function useId(id?: string) {
function useId(id?: string, forceUseId?: boolean) {
Comment on lines +51 to +52
// By default, test env always return mock id, but also allow force use id for test case which need to test id logic.
if (!forceUseId && process.env.NODE_ENV === 'test') {
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/hooks/useId.ts (1)

52-53: 建议补齐 forceUseId=true 的分支测试

Line 52 和 Line 76 新增了 !forceUseId 条件,但现有用例主要覆盖默认行为。建议新增测试:在 NODE_ENV='test' 下传 forceUseId=true 时,不应返回 'test-id'(React 18 分支和 compat 分支都应覆盖)。

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useId.ts` around lines 52 - 53, Add tests covering the
forceUseId=true branch so that when process.env.NODE_ENV === 'test' and the
hook/useId (function useId) is called with forceUseId set to true it does NOT
return the special 'test-id' value; add cases for both the React 18
implementation branch and the compat branch to assert a real/generated id is
returned (or not equal to 'test-id') and ensure NODE_ENV is set to 'test' only
for the test scope and restored afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/useId.ts`:
- Line 43: The exposed parameter forceUseId in useId is not being used by
internal callers, causing test runs to reuse the default 'test-id' and collide
keys in ignoredElementMap; update the internal calls to useId inside
useLockFocus (and the other occurrence noted) to explicitly pass true for
forceUseId so each instance generates/uses a unique id for keys stored in
ignoredElementMap, ensuring the new parameter actually takes effect.

---

Nitpick comments:
In `@src/hooks/useId.ts`:
- Around line 52-53: Add tests covering the forceUseId=true branch so that when
process.env.NODE_ENV === 'test' and the hook/useId (function useId) is called
with forceUseId set to true it does NOT return the special 'test-id' value; add
cases for both the React 18 implementation branch and the compat branch to
assert a real/generated id is returned (or not equal to 'test-id') and ensure
NODE_ENV is set to 'test' only for the test scope and restored afterward.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c51aad3a-f7dc-40a5-8fa5-ef999c0abe35

📥 Commits

Reviewing files that changed from the base of the PR and between 2a5333e and fe17cb0.

📒 Files selected for processing (1)
  • src/hooks/useId.ts

export default useOriginId
? // Use React `useId`
function useId(id?: string) {
function useId(id?: string, forceUseId?: boolean) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

forceUseId 已暴露但内部关键调用路径未接入,测试环境下仍可能发生 ID 冲突

src/Dom/focus.tsuseLockFocus(Line 224)仍是 useId() 调用,测试环境下默认仍会返回 'test-id',而它在 Line 238 用该值作为 ignoredElementMap 的 key,多个实例会互相覆盖。建议在该内部场景显式传入 true,让新参数真正生效。

建议修复(跨文件)
--- a/src/Dom/focus.ts
+++ b/src/Dom/focus.ts
@@
-  const id = useId();
+  const id = useId(undefined, true);

Also applies to: 59-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useId.ts` at line 43, The exposed parameter forceUseId in useId is
not being used by internal callers, causing test runs to reuse the default
'test-id' and collide keys in ignoredElementMap; update the internal calls to
useId inside useLockFocus (and the other occurrence noted) to explicitly pass
true for forceUseId so each instance generates/uses a unique id for keys stored
in ignoredElementMap, ensuring the new parameter actually takes effect.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/hooks-17.test.tsx (1)

60-72: 建议强化断言,直接覆盖“唯一 ID”目标。

Line 70 和 Line 71 目前只校验“不是 test-id”,若回归为其它固定值(例如同一个常量)也会误通过。建议一次渲染两个 DemoForce,再断言两个 id 不相等,这样更贴合本次修复“避免 id 冲突”的目标。

可参考的断言增强
 it('force use compat id in test env', () => {
   const DemoForce: React.FC<{ id?: string }> = ({ id }) => {
     const mergedId = useId(id, true);
     return <div id={mergedId} className="target-force" />;
   };

   resetUuid();
-  const { container } = render(<DemoForce />);
-  const ele = container.querySelector('.target-force');
-
-  expect(ele.id).toBeTruthy();
-  expect(ele.id).not.toEqual('test-id');
+  const { container } = render(
+    <>
+      <DemoForce />
+      <DemoForce />
+    </>,
+  );
+  const elements = container.querySelectorAll('.target-force');
+
+  expect(elements[0].id).toBeTruthy();
+  expect(elements[0].id).not.toEqual('test-id');
+  expect(elements[1].id).toBeTruthy();
+  expect(elements[1].id).not.toEqual('test-id');
+  expect(elements[0].id).not.toEqual(elements[1].id);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hooks-17.test.tsx` around lines 60 - 72, The test currently only
asserts the generated id is not 'test-id' which can miss regressions; update the
test for DemoForce (which uses useId(id, true)) to render two instances (after
resetUuid()), query both elements via '.target-force', assert each element has a
truthy id and then assert the two ids are not equal to guarantee uniqueness;
keep the existing resetUuid() call and retain the force-compat usage by calling
useId with true.
tests/hooks.test.tsx (1)

645-655: 建议把断言从“非 test-id”升级为“唯一且非 test-id”。

Line 653 和 Line 654 的覆盖面偏弱:如果实现错误地返回固定字符串(但不是 test-id),测试仍会通过。建议和上面的 compat 场景一致,验证两个实例的 id 不相等。

可参考的断言增强
 it('force use react useId in test env', () => {
   const DemoForce: React.FC<Readonly<{ id?: string }>> = ({ id }) => {
     const mergedId = useId(id, true);
     return <div id={mergedId} className="target-force" />;
   };

-  const { container } = render(<DemoForce />);
-  const ele = container.querySelector('.target-force');
-  expect(ele.id).toBeTruthy();
-  expect(ele.id).not.toEqual('test-id');
+  const { container } = render(
+    <>
+      <DemoForce />
+      <DemoForce />
+    </>,
+  );
+  const elements = container.querySelectorAll('.target-force');
+  expect(elements[0].id).toBeTruthy();
+  expect(elements[0].id).not.toEqual('test-id');
+  expect(elements[1].id).toBeTruthy();
+  expect(elements[1].id).not.toEqual('test-id');
+  expect(elements[0].id).not.toEqual(elements[1].id);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hooks.test.tsx` around lines 645 - 655, The test currently only asserts
the generated id is truthy and not equal to 'test-id'; update the
DemoForce/useId test to create two rendered instances (e.g., render two
<DemoForce /> or render twice) and assert both elements have ids that are
non-empty, not equal to 'test-id', and not equal to each other to ensure
uniqueness; locate the DemoForce component and the assertions around useId and
replace the single-instance checks with the pairwise uniqueness checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/hooks-17.test.tsx`:
- Around line 60-72: The test currently only asserts the generated id is not
'test-id' which can miss regressions; update the test for DemoForce (which uses
useId(id, true)) to render two instances (after resetUuid()), query both
elements via '.target-force', assert each element has a truthy id and then
assert the two ids are not equal to guarantee uniqueness; keep the existing
resetUuid() call and retain the force-compat usage by calling useId with true.

In `@tests/hooks.test.tsx`:
- Around line 645-655: The test currently only asserts the generated id is
truthy and not equal to 'test-id'; update the DemoForce/useId test to create two
rendered instances (e.g., render two <DemoForce /> or render twice) and assert
both elements have ids that are non-empty, not equal to 'test-id', and not equal
to each other to ensure uniqueness; locate the DemoForce component and the
assertions around useId and replace the single-instance checks with the pairwise
uniqueness checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d7a4ab77-eda8-4958-8d9e-c3d123935e96

📥 Commits

Reviewing files that changed from the base of the PR and between fe17cb0 and ddecc5e.

📒 Files selected for processing (3)
  • src/hooks/useId.ts
  • tests/hooks-17.test.tsx
  • tests/hooks.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/useId.ts

@yoyo837 yoyo837 closed this Mar 18, 2026
@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.20%. Comparing base (2a5333e) to head (ddecc5e).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #741   +/-   ##
=======================================
  Coverage   86.20%   86.20%           
=======================================
  Files          38       38           
  Lines        1044     1044           
  Branches      385      385           
=======================================
  Hits          900      900           
  Misses        142      142           
  Partials        2        2           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants